﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using PloobsEngine.SceneControl;
using PloobsEngine;
using PloobsEngine.Cameras;
using PloobsEngine.Light;

namespace Etapa1.LightExperiments
{
    public enum CalculationMethod
    {
        ROUGHNESS_LOOK_UP = 1,ROUGHNESS_BECKMANN = 2,ROUGHNESS_GAUSSIAN = 3
    }

    public class CookTorranceLightMap : IDeferredLightMap
    {
        private QuadRender quadRenderer;
        private EngineStuff engine;
        private RenderTarget2D lightRT;
        private Effect directionalLightEffect;
        private CalculationMethod calculationMethod = CalculationMethod.ROUGHNESS_BECKMANN;
        private Texture2D lookUp;

        public CalculationMethod CalculationMethod
        {
            get { return calculationMethod; }
            set { calculationMethod = value; }
        }
        

        private void DrawDirectionalLight(ICamera camera, IList<ILight> lights, IDeferredGBuffer DeferredGBuffer)
        {
            directionalLightEffect.Parameters["colorMap"].SetValue(DeferredGBuffer[GBufferTypes.COLOR]);
            directionalLightEffect.Parameters["normalMap"].SetValue(DeferredGBuffer[GBufferTypes.NORMAL]);
            directionalLightEffect.Parameters["depthMap"].SetValue(DeferredGBuffer[GBufferTypes.DEPH]);
            directionalLightEffect.Parameters["Extra1"].SetValue(DeferredGBuffer[GBufferTypes.Extra1]);            
            directionalLightEffect.Parameters["cameraPosition"].SetValue(camera.Position);
            directionalLightEffect.Parameters["InvertViewProjection"].SetValue(Matrix.Invert(camera.View * camera.Projection));
            directionalLightEffect.Parameters["halfPixel"].SetValue(EngineStuff.HalfPixel);
            directionalLightEffect.Parameters["method"].SetValue((int)calculationMethod);
            directionalLightEffect.Parameters["lookup"].SetValue(lookUp);            

            foreach (ILight item in lights)
            {
                if (item.LightType == LightType.Deferred_Directional)
                {   
                    DirectionalLight dl = item as DirectionalLight;

                    directionalLightEffect.Parameters["lightDirection"].SetValue(dl.LightDirection);
                    directionalLightEffect.Parameters["lightColor"].SetValue(dl.Color.ToVector3());
                    directionalLightEffect.Parameters["lightIntensity"].SetValue(dl.LightIntensity);

                    directionalLightEffect.Begin();
                    directionalLightEffect.Techniques[0].Passes[0].Begin();
                    quadRenderer.Render(Vector2.One * -1, Vector2.One);
                    directionalLightEffect.Techniques[0].Passes[0].End();
                    directionalLightEffect.End();

                }
            }
        }

        #region IDeferredLightMap Members

        public void DrawLights(Microsoft.Xna.Framework.GameTime gameTime, IWorld world, IDeferredGBuffer deferredGBuffer)
        {
            EngineStuff.GraphicsDevice.Clear(Color.TransparentBlack);
            EngineStuff.GraphicsDevice.RenderState.AlphaBlendEnable = true;
            EngineStuff.GraphicsDevice.RenderState.AlphaBlendOperation = BlendFunction.Add;
            EngineStuff.GraphicsDevice.RenderState.SourceBlend = Blend.One;
            EngineStuff.GraphicsDevice.RenderState.DestinationBlend = Blend.One;
            EngineStuff.GraphicsDevice.RenderState.SeparateAlphaBlendEnabled = false;
            EngineStuff.GraphicsDevice.RenderState.DepthBufferEnable = false;

            DrawDirectionalLight(world.ActiveCamera, world.Lights, deferredGBuffer);           

            EngineStuff.GraphicsDevice.RenderState.AlphaBlendEnable = false;
        }

        public void LoadContent(EngineStuff engine, bool useFloatBuffer)
        {
            this.engine = engine;

            quadRenderer = new QuadRender(EngineStuff.GraphicsDevice);
            if (useFloatBuffer)
                lightRT = EngineStuff.GetDefaultBuffer(SurfaceFormat.HalfVector4);
            else
                lightRT = EngineStuff.GetDefaultColorBuffer();

            lookUp = CreateRoughnessLookupTexture(engine);
            directionalLightEffect = EngineStuff.CustomContentManager.GetAsset<Effect>("..\\Content\\Effects\\CookTorranceIllumination");            
            
        }

        public Texture2D this[DeferredLightMapType type]
        {
            get
            {
                switch (type)
                {
                    case DeferredLightMapType.LIGHTMAP:
                        return lightRT.GetTexture();
                    default:
                        throw new Exception("Type not present in this implementation");
                }


            }
            set
            {
                throw new Exception("Cant do this in this implementation");
            }
        }

        #endregion

        #region IDeferredLightMap Members

        public void SetLightMap()
        {
            EngineStuff.GraphicsDevice.SetRenderTarget(0, lightRT);
        }

        public void ResolveLightMap()
        {
            EngineStuff.GraphicsDevice.SetRenderTarget(0, null);
        }


        private Texture2D CreateRoughnessLookupTexture(EngineStuff engine)
        {

            Texture2D t = new Texture2D(EngineStuff.GraphicsDevice, 250, 250,1,TextureUsage.AutoGenerateMipMap,SurfaceFormat.Single);
            float[] cor = new float[512 * 512];
            for (int i = 0; i < 250 ; i++)
            {
                for (int j = 0; j < 250; j++)
                {                    
                    float NdotH = (float) i / (float) 512;
                    float Roughness = (float)j / (float)512;
                    
                    NdotH *= 2.0f;
                    NdotH -= 1.0f;

                    // Evaluate a Beckmann distribution for this element
                    // of the look-up table:
                    float r_sq = Roughness * Roughness;
                    float r_a = 1.0f / (4.0f * r_sq * (float)Math.Pow(NdotH, 4));
                    float r_b = NdotH * NdotH - 1.0f;
                    float r_c = r_sq * NdotH * NdotH;

                    cor[i + j * 512] = r_a * (float)Math.Exp(r_b / r_c);                    

                }
            }

            t.SetData<float>(cor);
            return t;
        }

        #endregion
    }
}
